package com.ruoyi.mayi.aspect;

import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.GlobalException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.mayi.domain.ApiLog;
import com.ruoyi.mayi.factory.AsyncTaskFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

/**
 * Api 日志切面
 *
 * @author yacong_liu
 * @date 2022/2/12 17:46
 */
@Aspect
@Component
public class ApiLogAspect {

    private static final Logger log = LoggerFactory.getLogger(ApiLogAspect.class);

    public static final String OPERATE_SUCCESS = "0";
    public static final String OPERATE_ERROR = "1";

    @Value("${token.header}")
    private String header;

    @Around(value = "@annotation(com.ruoyi.mayi.annotation.ApiLog)")
    public Object around(ProceedingJoinPoint pjp) {

        ApiLog apiLog = new ApiLog();
        String tokenHeader = ServletUtils.getRequest().getHeader(this.header);
        // 通过请求头中是否携带有令牌标识Authorization来判定接口是否有登录认证，如有则记录接口用户
        if (StringUtils.isNotEmpty(tokenHeader)) {
            apiLog.setUser(SecurityUtils.getUsername());
        }
        apiLog.setDatetime(new Date());
        apiLog.setResult(OPERATE_SUCCESS);

        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        com.ruoyi.mayi.annotation.ApiLog apiLogAnnotation = method.getAnnotation(com.ruoyi.mayi.annotation.ApiLog.class);
        // 请求主题
        String title = apiLogAnnotation.value();
        // 请求地址
        String requestURI = ServletUtils.getRequest().getRequestURI();
        // 请求方法
        String requestMethod = ServletUtils.getRequest().getMethod();
        String requestInfo = title + "-->" + requestMethod.toUpperCase() + "-->" + requestURI;
        // 记录请求信息
        apiLog.setRequest(requestInfo);
        // 是否保存请求参数
        boolean isSaveRequestData = apiLogAnnotation.isSaveRequestData();
        // 是否保存请求响应信息
        boolean isSaveResponseData = apiLogAnnotation.isSaveResponseData();
        Object[] args = pjp.getArgs();

        // 判断是否保存请求参数到接口日志
        if (isSaveRequestData && StringUtils.isNotNull(args)) {
            apiLog.setParamater(StringUtils.substring(argsArrayToString(args), 0, 2000));
        }

        Object proceed;
        try {
            long startTime = System.currentTimeMillis();
            proceed = pjp.proceed();
            long endTime = System.currentTimeMillis();
            // 程序执行耗时 ms
            long cost = (endTime - startTime);
            apiLog.setCost(cost + "ms");
            // 判断是否保存请求响应到接口日志
            if (isSaveResponseData && StringUtils.isNotNull(proceed)) {
                apiLog.setResponse(StringUtils.substring(JSON.toJSONString(proceed), 0, 2000));
            }
            // 异步保存接口日志
            AsyncManager.me().execute(AsyncTaskFactory.recordApiLog(apiLog));

        } catch (Throwable e) {
            log.error("===Api 环绕通知异常 ===");
            log.error("异常信息：{}", e.getMessage());
            e.printStackTrace();
            apiLog.setResult(OPERATE_ERROR);
            apiLog.setError(e.toString());
            String err;
            if (e instanceof Exception) {
                err = stackTraceToString((Exception) e);
            } else {
                err = e.toString();
            }
            apiLog.setErrorstack(StringUtils.substring(err, 0, 2000));
            // 异步保存接口日志
            AsyncManager.me().execute(AsyncTaskFactory.recordApiLog(apiLog));
            throw new GlobalException(StringUtils.substring(err, 0, 1000));
        }

        return proceed;
    }

    /**
     * 堆栈信息转字符
     *
     * @param e 异常
     * @return 字符串
     */
    public static String stackTraceToString(Exception e) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        e.printStackTrace(printWriter);
        return stringWriter.toString();
    }


    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
                    Object jsonObj = JSON.toJSON(o);
                    params.append(jsonObj.toString()).append(" ");
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}
